package com.fr71.project.dm.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ZipUtil;
import com.alibaba.fastjson2.JSON;
import com.fr71.common.utils.DateUtils;
import com.fr71.common.utils.DictUtils;
import com.fr71.common.utils.ServletUtils;
import com.fr71.framework.manager.AsyncManager;
import com.fr71.framework.web.domain.AjaxResult;
import com.fr71.project.dm.domain.*;
import com.fr71.project.dm.listener.AuthTcpRequest;
import com.fr71.project.dm.listener.TcpInitChannel;
import com.fr71.project.dm.mapper.PdOrderMapper;
import com.fr71.project.dm.mapper.PdPluginDataMapper;
import com.fr71.project.dm.mapper.PdPluginErrMapper;
import com.fr71.project.dm.mapper.PdUserMapper;
import com.fr71.project.dm.service.*;
import com.fr71.project.dm.utils.*;
import com.fr71.project.dm.utils.pay_utils.AliPayUtil;
import com.fr71.project.dm.utils.pay_utils.LtzfPayUtil;
import com.fr71.project.dm.utils.pay_utils.WxPayUtil;
import com.fr71.project.system.domain.SysDictData;
import com.fr71.project.system.service.ISysConfigService;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.fr71.project.dm.domain.Beans.QQSignBean;
import com.fr71.project.dm.domain.Beans.EtcBean;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.WriteBufferWaterMark;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import kotlin.Triple;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.File;
import java.util.*;
import java.util.regex.Pattern;

@Service
public class PublicServiceImpl implements IPublicService {

    private static final Logger logger = LoggerFactory.getLogger(PublicServiceImpl.class);

    /**
     * 授权数据库查询 用户资料
     */
    @Autowired
    private PdUserMapper pdUserMapper;

    /**
     * 授权数据库查询 用户资料
     */
    @Autowired
    private IPdUserService iPdUserService;

    /**
     * 订单表查询
     */
    @Autowired
    private PdOrderMapper pdOrderMapper;

    /**
     * 插件封包
     */
    @Autowired
    private PdPluginDataMapper pdPluginDataMapper;

    /**
     * 插件异常
     */
    @Autowired
    private PdPluginErrMapper pdPluginErrMapper;

    /**
     * 换绑记录
     */
    @Autowired
    private IPdChangeLogService pdChangeLogService;

    /**
     * 系统配置
     */
    @Autowired
    private ISysConfigService sysConfigService;

    /**
     * 事件上报
     */
    @Autowired
    private IPdEventService eventService;

    /**
     * 鉴权日志
     */
    @Autowired
    private IAuthLogService authLogService;

    @Autowired
    private ISysConfigService configService;

    //全局共用的两个Netty线程池
    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup(6);

    private static final String CONFIG_AAA = "[CenterServer]\n" +
            "ServerIp=%s\n" +
            "DBPort=3306\n" +
            "DBUser=root\n" +
            "DBPassword=%s\n" +
            "DBName=asktao_center\n" +
            "[DBServer]\n" +
            "ListenIp=%s\n" +
            "ListenPort=8120\n" +
            "DBPort=3306\n" +
            "DBUser=root\n" +
            "DBPassword=%s\n" +
            "DBName=asktao_center";

    private static final String CONFIG_DBA = "[DBServer]\n" +
            "ListenIp=%s\n" +
            "ListenPort=8120\n" +
            "DBPort=3306\n" +
            "DBUser=root\n" +
            "DBPassword=%s\n" +
            "DBName=asktao_center\n" +
            "[CenterServer]\n" +
            "ServerIp=%s\n" +
            "DBPort=3306\n" +
            "DBUser=root\n" +
            "DBPassword=%s\n" +
            "DBName=asktao_center\n" +
            "[CrossServer]\n" +
            "ListenIp=%s\n" +
            "ListenPort=8110\n" +
            "DBPort=3306\n" +
            "DBUser=root\n" +
            "DBPassword=%s\n" +
            "DBName=asktao_cross\n" +
            "[AuthServer]\n" +
            "ListenIp=%s\n" +
            "ListenPort=%d";

    private static final String CONFIG_CCS = "[CrossServer]\n" +
            "ListenIp=%s\n" +
            "ListenPort=8110\n" +
            "DBPort=3306\n" +
            "DBUser=root\n" +
            "DBPassword=%s\n" +
            "DBName=asktao_cross\n" +
            "[CenterServer]\n" +
            "ServerIp=%s\n" +
            "DBPort=3306\n" +
            "DBUser=root\n" +
            "DBPassword=%s\n" +
            "DBName=asktao_center\n" +
            "[DBServer]\n" +
            "ListenIp=%s\n" +
            "ListenPort=8120\n" +
            "DBPort=3306\n" +
            "DBUser=root\n" +
            "DBPassword=%s\n" +
            "DBName=asktao_center\n" +
            "[CrossServer]\n" +
            "ListenIp=%s\n" +
            "ListenPort=8110\n" +
            "DBPort=3306\n" +
            "DBUser=root\n" +
            "DBPassword=%s\n" +
            "DBName=asktao_cross\n" +
            "[AuthServer]\n" +
            "ListenIp=%s\n" +
            "ListenPort=%d";

    private static final String CONFIG_CSA = "[CrossServer]\n" +
            "ListenIp=%s\n" +
            "ListenPort=8110\n" +
            "DBPort=3306\n" +
            "DBUser=root\n" +
            "DBPassword=%s\n" +
            "DBName=asktao_cross\n" +
            "[CenterServer]\n" +
            "ServerIp=%s\n" +
            "DBPort=3306\n" +
            "DBUser=root\n" +
            "DBPassword=%s\n" +
            "DBName=asktao_center\n" +
            "[DBServer]\n" +
            "ListenIp=%s\n" +
            "ListenPort=8120\n" +
            "DBPort=3306\n" +
            "DBUser=root\n" +
            "DBPassword=%s\n" +
            "DBName=asktao_center\n" +
            "[AuthServer]\n" +
            "ListenIp=%s\n" +
            "ListenPort=6101\n" +
            "DBPort=3306\n" +
            "DBUser=root\n" +
            "DBPassword=%s\n" +
            "DBName=asktao_auth\n" +
            "[AuthServer]\n" +
            "ListenIp=%s\n" +
            "ListenPort=%d";

    private static final String CONFIG_LINE = "[Me]\n" +
            "Name=%s\n" +
            "AAA_Addr=%s\n" +
            "AAA_Port=%s\n" +
            "\n" +
            "[FILE]\n" +
            "USE_DB = Enabled\n" +
            "\n" +
            "[Login]\n" +
            "EXPRESS_WAY = Enabled\n" +
            "MAX_USER = 512\n" +
            "\n" +
            "[REBOOT]\n" +
            "SERVER_PORT = %s\n" +
            "BROADCAST_INTERVAL = 5\n" +
            "BROADCAST_TIMES = 6\n" +
            "DENY_LOGIN_INTERVAL = 10";

    private static final String EXEC_LINE = "#!/bin/sh\n" +
            "cd /home/asktao/server/line/\n" +
            "chmod +x *\n" +
            "./%d";

    private static final String ALIPAY_RESULT_URL = "https://pay.fr71.com/api/public/alipayResult";
    private static final String WXPAY_RESULT_URL = "https://pay.fr71.com/api/public/wxpayResult";

    // 设置新的二级密码
    @Override
    public AjaxResult setNewPsw(String sCode, String newPwd) {
        PdUser user = pdUserMapper.getSCodeData(sCode);
        if (user != null) {
            user.setSCodePsw(newPwd);
            if (pdUserMapper.updatePdUser(user) > 0) {
                return AjaxResult.success();
            } else {
                return AjaxResult.error("操作失败，请联系管理员");
            }
        }
        return AjaxResult.error("激活码不存在");
    }

    // 上报插件错误异常
    @Override
    public AjaxResult postPluginErr(PdPluginErr pdPluginErr, String ip) {
        List<HashMap<String, Object>> versionInfos = this.getVersionInfos_();
        HashMap<String, Object> versionInfo = null;
        for (HashMap<String, Object> info : versionInfos) {
            if (Integer.parseInt(info.get("type").toString()) == 1) {
                versionInfo = info;
                break;
            }
        }
        
        int newVC = 0;
        if (versionInfo != null) {
            newVC = Integer.parseInt(versionInfo.get("versionCode").toString());
        }

        int vcode = 0;
        try {
            vcode = Integer.parseInt(pdPluginErr.getVersion().replace("v", "V").replace("V", "").replace(".", ""));
        } catch (Exception e) {
            vcode = 0;
        }

        if (vcode >= newVC) {
            pdPluginErr.setIp(ip);
            pdPluginErr.setCreateTime(DateUtils.getNowDate());
            pdPluginErrMapper.insertPdPluginErr(pdPluginErr);
        }

        return AjaxResult.success();
    }

    // 上报事件
    @Override
    public AjaxResult uploadEvent(HashMap<String, Object> data) {
        String sData = data.get("data") != null ? data.get("data").toString() : null;
        int type = Integer.parseInt(data.get("type").toString());
        String event = data.get("event").toString();

        PdEvent old;
        try {
            List<PdEvent> events = eventService.selectPdEventList(new PdEvent() {{
                setEventType(event);
                setType(type);
            }});
            old = events.get(0);
        } catch (Exception e) {
            old = new PdEvent();
            old.setData(sData);
            old.setType(type);
            old.setEventType(event);
            old.setTimes(0L);
        }

        old.setTimes(old.getTimes() + 1);

        if (old.getTimes() == 1L) {
            eventService.insertPdEvent(old);
        } else {
            eventService.updatePdEvent(old);
        }

        return AjaxResult.success();
    }

    // 使用QQ签到
    @Override
    public String doSignByQQ(String qq) {
        //查询qq的签到数据
        QQSignBean signData = RedisUtil.getQQSignData(qq);
        //检查今日是否已签到
        if (DateUtils.isSameDay(new Date(), new Date(signData.getLastSignTime()))) {
            return "今日已签到，请明日再来。/r" + signData.toString();
        }
        //没有签到可以签到 增加总的签到次数
        signData.setTotalSignNumber(signData.getTotalSignNumber() + 1);

        //是否有连续签到可以累加
        if (DateUtils.isSameDay(new Date(), DateUtils.addDays(new Date(signData.getLastSignTime()), 1))) {
            //对签到日期+1天，如果和当天日期一样，表示连续签到
            signData.setAcceptSignNumber(signData.getAcceptSignNumber() + 1);
        } else {
            signData.setAcceptSignNumber(1);
        }

        //结算积分 默认每天签到1积分
        signData.setTotalCoin(signData.getTotalCoin() + 1);

        //结算联系签到积分
        int accAddCoin = 0;
        switch (signData.getAcceptSignNumber()) {
            //连续签到3天额外得2积分
            case 3:
                accAddCoin = 2;
                break;
            case 5:
                accAddCoin = 3;
                break;
            case 10:
                accAddCoin = 5;
                break;
            case 20:
                accAddCoin = 10;
                break;
            case 30:
                accAddCoin = 20;
                break;
            case 60:
                accAddCoin = 30;
                break;
            case 90:
                accAddCoin = 40;
                break;
            case 120:
                accAddCoin = 50;
                break;
            case 150:
                accAddCoin = 60;
                break;
            case 180:
                accAddCoin = 70;
                break;
            case 210:
                accAddCoin = 80;
                break;
            case 240:
                accAddCoin = 90;
                break;
            case 270:
                accAddCoin = 100;
                break;
            case 300:
                accAddCoin = 110;
                break;
            case 330:
                accAddCoin = 120;
                break;
            case 360:
                accAddCoin = 365;
                break;
            default:
                accAddCoin = 0;
                break;
        }

        signData.setTotalCoin(signData.getTotalCoin() + accAddCoin);

        //标记最后签到时间
        signData.setLastSignTime(DateUtils.getNowDate().getTime());

        RedisUtil.setQQSignData(qq, signData);

        return "签到成功/r本次签到获得基础积分：1" + 
               (accAddCoin > 0 ? "/r连续签到" + signData.getAcceptSignNumber() + "天额外奖励积分：" + accAddCoin : "") + 
               "/r" + signData.toString();
    }

    // 领取体验卡
    @Override
    public AjaxResult getTryScode(String code, String bindIp) {
        return AjaxResult.error("活动目前关闭中，请稍后再试。");

        // 查询当月是否可以领取体验卡
        /*
        if (RedisUtil.canGetTryCodeTimes(code) > 0) {
            return AjaxResult.error("当月已领取体验卡，请勿重复领取");
        }

        if (!isIPv4Address(bindIp)) return AjaxResult.error("IP格式错误，无法领取！");

        List<PdUser> list = iPdUserService.selectPdUserList(new PdUser() {{
            setsCode(code);
        }});

        boolean isSucc = false;
        String tip = "未知错误";
        
        if (list.isEmpty()) {
            tip = "激活码不存在!";
        } else {
            PdUser data = list.get(0);

            boolean isAwasysUser = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, data.getAuthEndTime())
                    .startsWith("888");

            Date now = new Date();

            if (!isAwasysUser) {
                tip = "当前用户非永久VIP，无法领取体验卡";
            } else {
                //架设工具永久卡 每月可领取一次免费的三天卡
                if (data.getuType() == 0) {
                    //架设工具
                    //发一张周卡

                    String scode = DMUtils.generateUniqueSafCode();
                    try {
                        PdUser newUser = new PdUser();
                        newUser.setAuthType(0);
                        newUser.setuType(0);
                        newUser.setAuthEndTime(DateUtils.addDays(now, 7));
                        newUser.setBindIp(bindIp);
                        newUser.setAuthStartTime(now);
                        newUser.setChangeTimesMonth(1);
                        newUser.setsCode(scode);
                        newUser.setVersion("v1.0.0");
                        newUser.setCreateTime(now);
                        newUser.setRemark("领取的体验周卡");
                        
                        if (pdUserMapper.insertPdUser(newUser) > 0) {
                            tip = "领取成功，获得工具箱周卡：" + scode + "，绑定IP:" + bindIp;
                            isSucc = true;
                            RedisUtil.markGetTryTimes(code);
                        } else {
                            tip = "领取失败，该IP已有授权的卡类。";
                            DMUtils.removeKey(scode);
                        }
                    } catch (Exception e) {
                        tip = "领取失败，该IP已有授权的卡类。";
                        DMUtils.removeKey(scode);
                    }
                } else {
                    //插件永久卡 每月可领取时长按照累计使用时长计算

                    long ttt = data.getTotalUsedTime();
                    if ("Y".equals(data.getOnlines())) {
                        ttt += DateUtils.getNowDate().getTime() - data.getLastConnectTime().getTime();
                    }

                    Triple<Long, Long, Long> timeData = millisecondsToDaysHoursAndMinutes(ttt);
                    long useDay = timeData.getFirst();
                    long hours = timeData.getSecond();
                    long minutes = timeData.getThird();

                    logger.debug("使用天数(" + code + ")：" + useDay);
                    String scode = DMUtils.generateUniqueSafCode();
                    Date addDate = null;
                    
                    if (useDay >= 360) {
                        //使用一年以上  领取月卡
                        addDate = DateUtils.addMonths(now, 1);
                        tip = "领取成功，获得插件月卡:" + scode + "，绑定IP：" + bindIp;
                    } else if (useDay >= 180) {
                        //使用半年以上  领取20天卡
                        addDate = DateUtils.addDays(now, 20);
                        tip = "领取成功，获得插件20天卡:" + scode + "，绑定IP：" + bindIp;
                    } else if (useDay >= 90) {
                        //使用三个月以上  领取周卡
                        addDate = DateUtils.addDays(now, 7);
                        tip = "领取成功，获得插件周卡:" + scode + "，绑定IP：" + bindIp;
                    } else if (useDay >= 60) {
                        //使用两个月以上  领取3天卡
                        addDate = DateUtils.addDays(now, 3);
                        tip = "领取成功，获得插件3天卡:" + scode + "，绑定IP：" + bindIp;
                    } else if (useDay >= 30) {
                        //使用1个月以上  领取天卡
                        addDate = DateUtils.addDays(now, 1);
                        tip = "领取成功，获得插件1天卡:" + scode + "，绑定IP：" + bindIp;
                    } else {
                        DMUtils.removeKey(scode);
                        tip = "使用时长不足30天，无法领取(" + String.format("%02d天%02d小时%02d分钟", useDay, hours, minutes) + ")";
                    }

                    if (addDate != null) {
                        try {
                            PdUser newUser = new PdUser();
                            newUser.setAuthType(0);
                            newUser.setuType(1);
                            newUser.setAuthEndTime(addDate);
                            newUser.setBindIp(bindIp);
                            newUser.setAuthStartTime(now);
                            newUser.setChangeTimesMonth(1);
                            newUser.setCreateTime(now);
                            newUser.setsCode(scode);
                            newUser.setVersion("v1.0.0");
                            newUser.setRemark("领取的体验卡");
                            
                            if (pdUserMapper.insertPdUser(newUser) > 0) {
                                isSucc = true;
                                RedisUtil.markGetTryTimes(code);
                            } else {
                                tip = "领取失败，该IP已有授权的卡类。";
                                DMUtils.removeKey(scode);
                            }
                        } catch (Exception e) {
                            tip = "领取失败，该IP已有授权的卡类。";
                            DMUtils.removeKey(scode);
                        }
                    }
                }
            }
        }

        if (isSucc) {
            return AjaxResult.success("操作成功", tip + "!注意复制激活码保存，丢失后无法找回。");
        } else {
            return AjaxResult.error(tip);
        }
        */
    }

    private boolean isIPv4Address(String input) {
        String ipv4Regex = "^" +
                "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
                "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
                "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
                "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
        
        return Pattern.matches(ipv4Regex, input);
    }

    private Triple<Long, Long, Long> millisecondsToDaysHoursAndMinutes(Long milliseconds) {
        long totalMinutes = milliseconds / 1000 / 60;
        long days = totalMinutes / (24 * 60);
        long remainingMinutes = totalMinutes % (24 * 60);
        long hours = remainingMinutes / 60;
        long minutes = remainingMinutes % 60;
        return new Triple<>(days, hours, minutes);
    }

    // 根据qq查询绑定的工具箱或插件的有效期
    @Override
    public String getInfoByQQ(String qq) {
        List<PdUser> list = pdUserMapper.selectPdUserList(new PdUser() {{
            setBindQq(qq);
        }});

        if (list.isEmpty()) {
            return "未查询到绑定的工具箱或插件。";
        }

        String str = "";
        String str2 = "";

        for (PdUser user : list) {
            String end = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, user.getAuthEndTime());
            if (user.getUType() == 0) {
                str = "工具箱有效期：" + (end.startsWith("8888") ? "永久VIP" : end);
            }
            if (user.getUType() == 1) {
                str2 = "插件有效期：" + (end.startsWith("8888") ? "永久VIP" : end);
            }
        }
        
        if (!str2.isEmpty()) {
            return str + "/r" + str2;
        } else {
            return str;
        }
    }

    // 上报架设时长
    @Override
    public AjaxResult uploadInstallTime(String ip, Long delayTime) {
        String top10 = "";
        //获取当前用户
        PdUser user = pdUserMapper.getUserByIpAndUserType(ip, 0);
        if (user != null) {
            if (user.getLastInstallDelayTime() == 0L || user.getLastInstallDelayTime() > delayTime) {
                user.setLastInstallDelayTime(delayTime);
                user.setUpdateTime(DateUtils.getNowDate());
                pdUserMapper.updatePdUser(user);
            }

            //获取前5名
            List<PdUser> allUsers = pdUserMapper.selectPdUserList(null);
            List<PdUser> filteredUsers = new ArrayList<>();
            for (PdUser u : allUsers) {
                if (u.getLastInstallDelayTime() > 0 && u.getBindQq() != null && !u.getBindQq().isEmpty()) {
                    filteredUsers.add(u);
                }
            }
            
            // 排序
            filteredUsers.sort(Comparator.comparingLong(PdUser::getLastInstallDelayTime));
            
            List<PdUser> useTop = filteredUsers.size() > 5 ? filteredUsers.subList(0, 5) : filteredUsers;
            
            for (int index = 0; index < useTop.size(); index++) {
                PdUser pdUser = useTop.get(index);
                long totalSeconds = pdUser.getLastInstallDelayTime() / 1000;
                long minutes = totalSeconds / 60;
                long seconds = totalSeconds % 60;
                top10 += "第" + DigitalToChineseUtil.int2chineseNum(index + 1) + "名：架设耗时:" +
                        String.format("%02d分%02d秒", minutes, seconds) +
                        "，绑定QQ:" + getTmStr(pdUser.getBindQq()) + 
                        (pdUser.getBindIp().equals(ip) ? " <- 你的排名" : "") + 
                        (index != useTop.size() - 1 ? "\n" : "");
            }
        }

        return AjaxResult.success("操作成功", top10);
    }

    private String getTmStr(String str) {
        if (str == null || str.isEmpty()) return "";

        if (str.length() - 6 > 0) {
            StringBuilder a = new StringBuilder();
            for (int i = 0; i < str.length() - 6; i++) {
                a.append("*");
            }
            return str.substring(0, 3) + a + str.substring(str.length() - 3);
        } else {
            return str;
        }
    }

    // 授权查询
    // [uType] 0工具箱 1插件
    @Override
    public AjaxResult checkAuth(String ip, Integer uType, String version) {
        AuthLog authLog = new AuthLog();
        authLog.setIp(ip);
        authLog.setUt(uType);
        authLog.setCv(version);
        authLog.setCreateTime(DateUtils.getNowDate());

        //检查版本 限制插件更新
        if (uType == 1 && !"v1.0.0".equals(version)) {
            List<HashMap<String, Object>> versionInfos = this.getVersionInfos_();
            HashMap<String, Object> vb = null;
            for (HashMap<String, Object> info : versionInfos) {
                if (Integer.parseInt(info.get("type").toString()) == uType) {
                    vb = info;
                    break;
                }
            }
            
            if (vb != null) {
                Object newVersionCodeObj = vb.get("versionCode");
                Object newfouceObj = vb.get("fouce");
                
                if (newVersionCodeObj != null && newfouceObj != null) {
                    String newVersionCode = newVersionCodeObj.toString();
                    String newfouce = newfouceObj.toString();
                    
                    int vcode = 0;
                    try {
                        vcode = Integer.parseInt(version.replace("v", "V").replace("V", "").replace(".", ""));
                    } catch (Exception e) {
                        vcode = 0;
                    }

                    if ("Y".equals(newfouce) && vcode < Integer.parseInt(newVersionCode)) {
                        authLog.setStatus("N");
                        authLog.setRemark("版本需要更新，请更新后再试！");
                        authLogService.insertAuthLog(authLog);
                        return AjaxResult.error("版本需要更新，请更新后再试！");
                    }
                }
            }
        }

        PdUser user = pdUserMapper.getUserByIpAndUserType(ip, uType);
        if (user != null) {
            //v1.0.0 在工具箱安装插件前会调用一次这个接口，用ip检查授权信息。通过的才给安装，送的版本就是v1.0.0,授权验证通过后插件本身在启动的时候也会进行一次校验。
            if (!"v1.0.0".equals(version)) {
                user.setVersion(version);
                pdUserMapper.updatePdUser(user);
            }

            if ("Y".equals(user.getBlack())) {
                authLog.setStatus("N");
                authLog.setRemark(user.getBlackDec());
                authLogService.insertAuthLog(authLog);
                return AjaxResult.error(user.getBlackDec());
            }

            if (new Date().before(user.getAuthEndTime())) {
                if (uType == 0) {
                    //工具箱，返回用户实体就好
                    authLog.setStatus("Y");
                    authLog.setScode(user.getSCode());
                    authLog.setBindqq(user.getBindQq());
                    authLog.setRemark("授权通过");
                    authLogService.insertAuthLog(authLog);
                    return AjaxResult.success(user);
                } else {
                    //返回绑定的数据
                    authLog.setStatus("Y");
                    authLog.setScode(user.getSCode());
                    authLog.setBindqq(user.getBindQq());
                    authLog.setRemark("授权通过并附加加密数据");
                    authLogService.insertAuthLog(authLog);
                    return doCheckPlugin(user);
                }
            } else {
                authLog.setStatus("N");
                authLog.setScode(user.getSCode());
                authLog.setBindqq(user.getBindQq());
                authLog.setRemark(ip + "的授权到期，请重新购买授权。");
                authLogService.insertAuthLog(authLog);
                return AjaxResult.error(ip + "的授权到期，请重新购买授权。");
            }
        }

        authLog.setStatus("N");
        authLog.setRemark(ip + "的安全验证失败，请购买授权后尝试。");
        authLogService.insertAuthLog(authLog);
        return AjaxResult.error(ip + "的安全验证失败，请购买授权后尝试。");
    }

    // 插件鉴权通过后返回的数据
    private AjaxResult doCheckPlugin(PdUser user) {
        HashMap<String, String> data = new HashMap<>();

        List<PdPluginData> pluginDataList = pdPluginDataMapper.selectPdPluginDataList(null);
        for (PdPluginData pluginData : pluginDataList) {
            data.put(DMUtils.encrypt(pluginData.getType()), DMUtils.encrypt(String.valueOf(pluginData.getData())));
        }

        HashMap<String, Object> result = new HashMap<>();
        //封包
        result.put("a2", data);
        //用户数据
        result.put("d1", user);
        result.put("v", getVersionInfos_().stream()
                .filter(info -> Integer.parseInt(info.get("type").toString()) == 1)
                .findFirst()
                .orElse(null));
        result.put("ct", DMUtils.encrypt(String.valueOf(new Date().getTime())));

        return AjaxResult.success(result);
    }

    // 根据安全码查询绑定的IP和到期时间
    @Override
    public AjaxResult getSCodeInfo(String sCode) {
        PdUser user = pdUserMapper.getSCodeData(sCode);
        if (user != null) {
            return AjaxResult.success(user);
        }
        return AjaxResult.error("未查询到授权码相关数据");
    }

    @PostConstruct
    public void onInit() {
        List<PdUser> users = iPdUserService.selectPdUserList(new PdUser() {{
            setOnlines("Y");
        }});
        
        for (PdUser user : users) {
            user.setOnlines("N");
            iPdUserService.updatePdUser(user);
        }
        
        AsyncManager.me().execute(new TimerTask() {
            @Override
            public void run() {
                ServerBootstrap bootstrap = new ServerBootstrap();
                bootstrap.group(bossGroup, workerGroup)
                        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                        //表示 TCP 连接请求的队列大小，一般情况下默认为 50。如果处理的请求比较繁忙，可以考虑增大该值。
                        .option(ChannelOption.SO_BACKLOG, 1024)
                        //表示接收缓冲区大小，一般情况下默认为 8KB。如果处理的请求数据量比较大，可以适当增大该值。
                        .option(ChannelOption.SO_RCVBUF, 1024 * 1024)
                        //WRITE_BUFFER_WATER_MARK：表示写缓冲区的水位线，当写缓冲区数据大小超过该值时，会触发 channelWritable 事件，从而触发写操作。默认为低水位 32768，高水位 65536。
                        .option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(1024 * 512, 1024 * 1024))
                        .channel(NioServerSocketChannel.class)
                        .childHandler(new TcpInitChannel(new AuthTcpRequest(iPdUserService)));
                
                bootstrap.bind(8889);
            }
        });
    }

    @Override
    public AjaxResult uploadAsw(Object asw, Object aswTime, String ip) {
        List<String> aswList = (List<String>) asw;
        long aswL = Long.parseLong(aswTime.toString());
        List<SysDictData> qs = DictUtils.getDictCache("questions");
        
        if (aswList.size() != qs.size()) {
            return AjaxResult.success("服务端答题卡配置异常，请刷新页面重新答题。");
        }

        //每题答对的分数
        double indexScore = 100.0 / qs.size();
        double totalScore = 0.0;
        int aswSuccTimes = 0;
        
        for (int index = 0; index < qs.size(); index++) {
            SysDictData sysDictData = qs.get(index);
            if (sysDictData.getDictValue().equals(aswList.get(index))) {
                totalScore += indexScore;
                aswSuccTimes++;
            }
        }
        
        //更新分数
        List<PdUser> users = iPdUserService.selectPdUserList(new PdUser() {{
            setBindIp(ip);
            setUType(1);
        }});
        
        for (PdUser user : users) {
            user.setTotalAswScore(String.format("%.2f", totalScore));
            user.setAswTime(aswL);
            iPdUserService.updatePdUser(user);
        }
        
        long totalSeconds = aswL / 1000;
        long minutes = totalSeconds / 60;
        long seconds = totalSeconds % 60;

        HashMap<String, Object> result = new HashMap<>();
        result.put("questionTop", getQuestTop(ip));
        result.put("tip", "你本次答对" + aswSuccTimes + "道题目，耗时：" +
                String.format("%02d分%02d秒", minutes, seconds) +
                ",共计得分：" + String.format("%.2f", totalScore) + "分！");

        return AjaxResult.success(result);
    }

    private String getQuestTop(String ip) {
        //获取前5名
        List<PdUser> allUsers = iPdUserService.selectPdUserList(new PdUser() {{
            setUType(1);
        }});
        
        List<PdUser> filteredUsers = new ArrayList<>();
        for (PdUser user : allUsers) {
            if (user.getTotalAswScore() != null && !user.getTotalAswScore().isEmpty() && 
                Float.parseFloat(user.getTotalAswScore()) > 0) {
                filteredUsers.add(user);
            }
        }

        // 排序
        filteredUsers.sort((u1, u2) -> {
            float score1 = Float.parseFloat(u1.getTotalAswScore());
            float score2 = Float.parseFloat(u2.getTotalAswScore());
            return Float.compare(score2, score1); // 降序排列
        });

        StringBuilder top5 = new StringBuilder();
        List<PdUser> useTop = filteredUsers.size() > 5 ? filteredUsers.subList(0, 5) : filteredUsers;
        
        for (int index = 0; index < useTop.size(); index++) {
            PdUser pdUser = useTop.get(index);
            long totalSeconds = pdUser.getAswTime() / 1000;
            long minutes = totalSeconds / 60;
            long seconds = totalSeconds % 60;
            top5.append("第").append(DigitalToChineseUtil.int2chineseNum(index + 1)).append("名：耗时:")
                    .append(String.format("%02d分%02d秒", minutes, seconds))
                    .append("，得分：").append(pdUser.getTotalAswScore())
                    .append(",绑定QQ:").append(getTmStr(pdUser.getBindQq()))
                    .append(pdUser.getBindIp().equals(ip) ? "  <- 你的排名" : "")
                    .append(index != useTop.size() - 1 ? "\n" : "");
        }

        return top5.toString();
    }

    @Override
    public AjaxResult getHomeData(String ip) {
        List<HashMap<String, Object>> list = new ArrayList<>();
        List<SysDictData> questions = DictUtils.getDictCache("questions");
        
        for (SysDictData dictData : questions) {
            HashMap<String, Object> question = new HashMap<>();
            question.put("text", dictData.getDictLabel());
            question.put("selectList", JSON.parseArray(dictData.getRemark()));
            list.add(question);
        }
        
        //查询全部问题列表
        HashMap<String, Object> result = new HashMap<>();
        result.put("versionInfo", getVersionInfos_().stream()
                .filter(info -> Integer.parseInt(info.get("type").toString()) == 1)
                .findFirst()
                .orElse(null));
        result.put("noticeInfo", getNotices_().stream()
                .filter(info -> Integer.parseInt(info.get("type").toString()) == 1)
                .findFirst()
                .orElse(null));
        result.put("questions", list);
        result.put("questionTop", getQuestTop(ip));
        
        Object alreadyAswScore;
        try {
            List<PdUser> users = iPdUserService.selectPdUserList(new PdUser() {{
                setBindIp(ip);
                setUType(1);
            }});
            alreadyAswScore = users.get(0).getTotalAswScore();
        } catch (Exception e) {
            e.printStackTrace();
            alreadyAswScore = 0;
        }
        result.put("alreadyAswScore", alreadyAswScore);

        return AjaxResult.success(result);
    }

    @PreDestroy
    public void onUnInit() {
        try {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
            logger.debug("关闭验证TCP监听");
        } catch (Exception e) {
            logger.error("关闭tcp端口异常", e);
        }
    }

    // 前端用来获取商品列表数据
    @Override
    public AjaxResult listPrice() {
        List<Beans.ResultShopBean> resultList = new ArrayList<>();
        
        List<SysDictData> linuxTools = DictUtils.getDictCache("linux_tools");
        for (SysDictData dictData : linuxTools) {
            resultList.add(new Beans.ResultShopBean(dictData.getDictLabel(), 0, dictData.getDictValue(), dictData.getDictCode()));
        }
        
        List<SysDictData> linuxManager = DictUtils.getDictCache("linux_manager");
        for (SysDictData dictData : linuxManager) {
            resultList.add(new Beans.ResultShopBean(dictData.getDictLabel(), 1, dictData.getDictValue(), dictData.getDictCode()));
        }

        boolean open = Boolean.parseBoolean(configService.selectConfigByKey("openPay"));
        String errTip = configService.selectConfigByKey("closeTip");
        if (errTip == null) {
            errTip = "网站关闭中，暂不开启。";
        }

        HashMap<String, Object> result = new HashMap<>();
        result.put("open", open);
        result.put("errTip", errTip);
        result.put("data", resultList);

        return AjaxResult.success(result);
    }

    // 前端页面生成订单
    @Override
    public AjaxResult createOrder(Map<String, Object> req) {
        //新买激活码填写服务器IP
        //续费激活码输入服务器IP或安全码
        //buyType buy/add
        Object buyTypeObj = req.get("buyType");

        //qq
        Object qqObj = req.get("qq");
        String qq = qqObj != null ? qqObj.toString() : "";

        if (buyTypeObj == null || (!"buy".equals(buyTypeObj) && !"add".equals(buyTypeObj))) {
            return AjaxResult.error("购买类型异常");
        }

        String buyType = buyTypeObj.toString();
        String sCode = "";
        String reqIp = ServletUtils.getRequest().getHeader("x-real-ip");
        if (reqIp == null) {
            reqIp = ServletUtils.getRequest().getRemoteAddr();
        }

        //支付方式 alipay wxpay
        String payType = req.get("payType").toString();

        //类型 0 工具箱 1 插件
        int type = Integer.parseInt(req.get("type").toString());

        //插件或工具箱绑定的IP
        String bindIp;
        if ("buy".equals(buyType)) {
            Object bindIpObj = req.get("bindIp");
            bindIp = bindIpObj != null ? bindIpObj.toString() : "";
            //用绑定ip去查询 能查询到的时候 不能新购 续费只能用激活码
            PdUser alreadyHasUser = pdUserMapper.getUserByIpAndUserType(bindIp, type);
            if (alreadyHasUser != null) {
                if (new Date().before(alreadyHasUser.getAuthEndTime())) {
                    return AjaxResult.error("IP已被绑定且在有效期内，无法完成新购，请使用安全码续费或联系管理员。");
                } else {
                    //已经存在了这个ip，但是已经到期了，可能是忘了激活码了，这里需要重新生成激活码完成新购
                    sCode = DMUtils.generateUniqueSafCode();
                }
            } else {
                sCode = DMUtils.generateUniqueSafCode();
            }
        } else {
            //续费
            //根据安全码查询出绑定的IP
            Object sCodeObj = req.get("sCode");
            sCode = sCodeObj != null ? sCodeObj.toString() : "";
            PdUser oldUser = pdUserMapper.getSCodeData(sCode);
            if (oldUser != null && oldUser.getUType() == type) {
                sCode = oldUser.getSCode();
                bindIp = oldUser.getBindIp();
            } else {
                return AjaxResult.error("续费的激活码不存在，新购激活码只需要填写服务器IP即可。");
            }
        }

        if ("buy".equals(buyType) && (bindIp == null || bindIp.isEmpty())) {
            return AjaxResult.error("新买授权时必须填写服务器IP");
        }

        if ("add".equals(buyType) && (sCode == null || sCode.isEmpty())) {
            return AjaxResult.error("续费时必须填写授权码");
        }

        //价格字典编号
        long priceCode = Long.parseLong(req.get("priceCode").toString());

        List<SysDictData> dictList = DictUtils.getDictCache(type == 0 ? "linux_tools" : "linux_manager");

        //根据字典编号取出价格
        SysDictData price = null;
        for (SysDictData dictData : dictList) {
            if (dictData.getDictCode() == priceCode) {
                price = dictData;
                break;
            }
        }

        if (price == null) {
            return AjaxResult.error("所购商品不存在");
        }

        String buyTip = "购买【" + price.getDictLabel() + "】";
        String orderId = UUID.randomUUID().toString().replace("-", "").toLowerCase();

        String out_trade_no = "";

        String payUrl = null;
        if ("alipay".equals(payType)) {
            // TODO: 实现支付宝支付逻辑
            // AliPayUtil.createOrder(orderId, ALIPAY_RESULT_URL, buyTip, price.dictValue.toInt())
            //     ?.also { out_trade_no = it.outTradeNo }?.qrCode
        } else if ("wxpay".equals(payType)) {
            out_trade_no = orderId;
            // TODO: 实现微信支付逻辑
            // WxPayUtil.createOrder(orderId, WXPAY_RESULT_URL, buyTip, price.dictValue.toInt())
            // LtzfPayUtil.createOrder(orderId, WXPAY_RESULT_URL, buyTip, price.dictValue)
        } else {
            return AjaxResult.error("不支持的付款方式");
        }

        if (payUrl == null || out_trade_no.isEmpty()) {
            return AjaxResult.error("不支持的付款方式");
        }

        PdOrder order = new PdOrder();
        order.setOrderId(orderId);
        order.setOrderStatus(0);
        order.setOrderType(type);
        order.setCreateTime(DateUtils.getNowDate());
        order.setBindIp(bindIp);
        order.setPriceCode((int) priceCode);
        order.setsCode(sCode);
        order.setPrice(price.getDictValue());
        order.setTradeNo(out_trade_no);
        order.setPayData(payUrl);
        order.setBuyType(buyType);
        order.setIp(reqIp);
        order.setQq(qq);
        order.setPayType(payType);

        if (pdOrderMapper.insertPdOrder(order) > 0) {
            //打一个标记，用订单号
            RedisUtil.markOrder(orderId);
            //返回订单剩余可支付时间
            HashMap<String, Object> result = new HashMap<>();
            result.put("payUrl", payUrl);
            result.put("orderExpTime", 3 * 60);
            result.put("orderId", orderId);
            return AjaxResult.success(result);
        } else {
            return AjaxResult.error("生成失败，请稍后重试！");
        }
    }

    // 微信支付回掉
    @Override
    public String wxpayResult(String nonce, String signature, String timestamp, String serial, String body) {
        // TODO: 实现微信支付回调逻辑
        /*
        WxPayUtil.onPayResult(nonce, signature, timestamp, serial, body)?.let {
            if (it.tradeState.ordinal == Transaction.TradeStateEnum.SUCCESS.ordinal) {
                onPayResult(it.outTradeNo);
            }
        }
        */
        return "success";
    }

    // 支付回调
    @Override
    public String alipayResult(HashMap<String, String> map) {
        // TODO: 实现支付宝支付回调逻辑
        /*
        AliPayUtil.onPayResult(map)

        val trade_status = map["trade_status"]!!
        val orderId = map["out_trade_no"]!!

        if (trade_status == "TRADE_SUCCESS") {
            onPayResult(orderId)
        }
        */
        return "success";
    }

    @Override
    public String ltzfResult(HashMap<String, String> map) {
        String trade_status = map.get("code");
        String orderId = map.get("out_trade_no");

        if ("0".equals(trade_status)) {
            onPayResult(orderId);
        }
        return "SUCCESS";
    }

    private void onPayResult(String orderId) {
        PdOrder order = pdOrderMapper.selectPdOrderByOrderId(orderId);

        if (order != null && order.getOrderStatus() == 0) {
            //删除过期的标记
            RedisUtil.delOrderMark(orderId);
            logger.debug("订单过期标志已删除");

            //写入用户
            List<SysDictData> labels = DictUtils.getDictCache(order.getOrderType() == 0 ? "linux_tools" : "linux_manager");
            SysDictData label = null;
            for (SysDictData dictData : labels) {
                if (Objects.equals(dictData.getDictCode(), order.getPriceCode())) {
                    label = dictData;
                    break;
                }
            }

            PdUser old = pdUserMapper.getUserByIpAndUserType(order.getBindIp(), order.getOrderType());

            Date ot = old != null ? old.getAuthEndTime() : DateUtils.addYears(new Date(), -100);

            //以月为单位
            Date endTime = null;
            if (label != null) {
                String dictLabel = label.getDictLabel();
                if (dictLabel.contains("月卡")) {
                    endTime = DateUtils.addMonths(
                            new Date().before(ot) ? ot : DateUtils.getNowDate(),
                            1
                    );
                } else if (dictLabel.contains("季卡")) {
                    endTime = DateUtils.addMonths(
                            new Date().before(ot) ? ot : DateUtils.getNowDate(),
                            3
                    );
                } else if (dictLabel.contains("天卡")) {
                    endTime = DateUtils.addDays(
                            new Date().before(ot) ? ot : DateUtils.getNowDate(),
                            1
                    );
                } else if (dictLabel.contains("半年卡")) {
                    endTime = DateUtils.addMonths(
                            new Date().before(ot) ? ot : DateUtils.getNowDate(),
                            6
                    );
                } else if (dictLabel.contains("年卡")) {
                    endTime = DateUtils.addYears(
                            new Date().before(ot) ? ot : DateUtils.getNowDate(),
                            1
                    );
                } else if (dictLabel.contains("永久卡")) {
                    endTime = DateUtils.addYears(
                            new Date().before(ot) ? ot : DateUtils.getNowDate(),
                            1000
                    );
                }
            }

            String remark = "订单已支付完成";
            if (endTime != null) {
                if (old != null) {
                    //更新
                    old.setUpdateTime(DateUtils.getNowDate());
                    old.setAuthEndTime(endTime);
                    if (order.getQq() != null && !order.getQq().isEmpty()) {
                        old.setBindQq(order.getQq());
                    }
                    if (!old.getSCode().equals(order.getsCode())) {
                        old.setSCode(order.getsCode());
                    }
                    old.setChangeTimesMonth(0);
                    old.setRemark("充值重置换绑次数");

                    if (pdUserMapper.updatePdUser(old) > 0) {
                        //更新成功
                        logger.debug("更新成功：" + JSON.toJSONString(old));
                    } else {
                        remark = "更新旧用户失败";
                        logger.debug("更新失败：" + JSON.toJSONString(old));
                    }
                } else {
                    //写入
                    PdUser newUser = new PdUser();
                    newUser.setUpdateTime(DateUtils.getNowDate());
                    newUser.setCreateTime(newUser.getUpdateTime());
                    newUser.setBindIp(order.getBindIp());
                    newUser.setAuthStartTime(DateUtils.getNowDate());
                    newUser.setAuthEndTime(endTime);
                    newUser.setSCode(order.getsCode());
                    newUser.setUType(order.getOrderType());
                    newUser.setAuthType(0);
                    newUser.setChangeTimesMonth(0);
                    newUser.setVersion("v1.0.0");
                    if (order.getQq() != null && !order.getQq().isEmpty()) {
                        newUser.setBindQq(order.getQq());
                    }

                    if (pdUserMapper.insertPdUser(newUser) <= 0) {
                        remark = "写入新用户失败";
                        logger.error("写入用户失败");
                    }
                }

            } else {
                remark = "过期时间下发异常";
                logger.error("过期时间下发异常");
            }

            //写入成功
            order.setRemark(remark);
            order.setPayTime(DateUtils.getNowDate());
            order.setOrderStatus(1);
            pdOrderMapper.updatePdOrder(order);

        } else {
            logger.error("未找到订单数据");
        }
    }

    // 获取订单状态
    @Override
    public AjaxResult getOrderStatus(String orderId) {
        PdOrder order = pdOrderMapper.selectPdOrderByOrderId(orderId);
        if (order != null && order.getOrderStatus() == 1) {
            //支付成功并且写入了绑定数据
            //查询用户数据
            HashMap<String, Object> result = new HashMap<>();
            result.put("order", order);
            result.put("user", pdUserMapper.getUserByIpAndUserType(order.getBindIp(), order.getOrderType()));
            return AjaxResult.success(result);
        }
        return AjaxResult.error("未查询到订单信息");
    }

    private List<HashMap<String, Object>> getVersionInfos_() {
        List<HashMap<String, Object>> list = RedisUtil.getVersionList();
        if (list == null || list.isEmpty()) {
            list = new ArrayList<>();
            HashMap<String, Object> toolVersion = new HashMap<>();
            toolVersion.put("type", 0);
            toolVersion.put("versionCode", 0);
            toolVersion.put("versionName", "v1.0.0");
            toolVersion.put("updateTime", DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getNowDate()));
            toolVersion.put("content", "工具箱新版本");
            toolVersion.put("downloadUrl", "https://oss-12311.oss-cn-beijing.aliyuncs.com/2025/fg");
            toolVersion.put("fouce", "Y");
            
            HashMap<String, Object> pluginVersion = new HashMap<>();
            pluginVersion.put("type", 1);
            pluginVersion.put("versionCode", 0);
            pluginVersion.put("versionName", "v1.0.0");
            pluginVersion.put("updateTime", DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getNowDate()));
            pluginVersion.put("content", "插件新版本");
            pluginVersion.put("downloadUrl", "https://oss-12311.oss-cn-beijing.aliyuncs.com/2025/plugin.jar");
            pluginVersion.put("fouce", "Y");
            
            list.add(toolVersion);
            list.add(pluginVersion);
        }

        return list;
    }

    // 更新版本信息
    @Override
    public AjaxResult setVersionInfos(List<HashMap<String, Object>> list) {
        RedisUtil.setVersionInfos(list);
        return AjaxResult.success();
    }

    // 获取版本号
    @Override
    public AjaxResult getVersionInfos() {
        return AjaxResult.success(getVersionInfos_());
    }

    // app端调用 获取版本信息
    @Override
    public AjaxResult getVersionInfoByType(Integer type) {
        List<HashMap<String, Object>> versionInfos = getVersionInfos_();
        for (HashMap<String, Object> info : versionInfos) {
            if (Integer.parseInt(info.get("type").toString()) == type) {
                return AjaxResult.success(info);
            }
        }
        return AjaxResult.error("未找到版本信息");
    }

    // 获取公告
    private List<HashMap<String, Object>> getNotices_() {
        List<HashMap<String, Object>> list = RedisUtil.getNoticeList();
        if (list == null || list.isEmpty()) {
            list = new ArrayList<>();
            HashMap<String, Object> toolNotice = new HashMap<>();
            toolNotice.put("type", 0);
            toolNotice.put("content", "工具箱公告");
            
            HashMap<String, Object> pluginNotice = new HashMap<>();
            pluginNotice.put("type", 1);
            pluginNotice.put("content", "插件公告");
            
            list.add(toolNotice);
            list.add(pluginNotice);
        }

        return list;
    }

    // 更新公告
    @Override
    public AjaxResult setNoticesList(List<HashMap<String, Object>> list) {
        RedisUtil.setNoticesList(list);
        return AjaxResult.success();
    }

    // 获取公告
    @Override
    public AjaxResult getNoticeList() {
        return AjaxResult.success(getNotices_());
    }

    // app端调用 获取公告
    @Override
    public AjaxResult getNoticeByType(Integer type) {
        List<HashMap<String, Object>> notices = getNotices_();
        for (HashMap<String, Object> notice : notices) {
            if (Integer.parseInt(notice.get("type").toString()) == type) {
                return AjaxResult.success(notice);
            }
        }
        return AjaxResult.error("未找到公告信息");
    }

    // 根据安全码换绑
    @Override
    public AjaxResult changeBindIpBySCode(String newIp, String sCode, Integer type) {
        //10分钟内超过10次即封ip10分钟
        if (RedisUtil.getTryTimes(newIp) > 10) {
            String tip = "操作失败，Ip黑名单，请稍后再试。";
            PdChangeLog changeLog = new PdChangeLog();
            changeLog.setChangeStatus("N");
            changeLog.setType(type);
            changeLog.setFromIp("");
            changeLog.setToIp(newIp);
            changeLog.setsCode(sCode);
            changeLog.setStatusMsg(tip);
            pdChangeLogService.insertPdChangeLog(changeLog);
            return AjaxResult.error(tip);
        }

        PdUser user = pdUserMapper.getSCodeData(sCode);
        if (user != null) {
            String oldIp = user.getBindIp();

            if ("Y".equals(user.getBlack())) {
                PdChangeLog changeLog = new PdChangeLog();
                changeLog.setChangeStatus("N");
                changeLog.setType(type);
                changeLog.setFromIp(oldIp);
                changeLog.setToIp(newIp);
                changeLog.setsCode(sCode);
                changeLog.setStatusMsg(user.getBlackDec());
                pdChangeLogService.insertPdChangeLog(changeLog);
                return AjaxResult.error(user.getBlackDec());
            }

            if (new Date().before(user.getAuthEndTime())) {
                if (user.getUType() == type) {
                    //查询已换绑次数是否超过了限制
                    int maxChangeTimes = Integer.parseInt(sysConfigService.selectConfigByKey("can_change_time_every_month"));
                    if (user.getChangeTimesMonth() >= maxChangeTimes) {
                        PdChangeLog changeLog = new PdChangeLog();
                        changeLog.setChangeStatus("N");
                        changeLog.setType(type);
                        changeLog.setFromIp(user.getBindIp());
                        changeLog.setToIp(newIp);
                        changeLog.setsCode(sCode);
                        changeLog.setStatusMsg("超过当月最大可换绑次数，请下月1日再试！");
                        pdChangeLogService.insertPdChangeLog(changeLog);
                        return AjaxResult.error("超过当月最大可换绑次数，请下月1日再试！可在续费页面对激活码进行续费任意金额即可清空本月换绑次数。https://pay.fr71.com");
                    }

                    //可以换绑 修改IP
                    user.setBindIp(newIp);
                    //修改换绑次数+1
                    user.setChangeTimesMonth(user.getChangeTimesMonth() + 1);

                    boolean updateStatus = pdUserMapper.updatePdUser(user) > 0;

                    PdChangeLog changeLog = new PdChangeLog();
                    changeLog.setChangeStatus(updateStatus ? "Y" : "N");
                    changeLog.setType(type);
                    changeLog.setFromIp(oldIp);
                    changeLog.setToIp(newIp);
                    changeLog.setsCode(sCode);
                    changeLog.setStatusMsg(updateStatus ? "操作成功" : "换绑错误，请联系管理员");
                    pdChangeLogService.insertPdChangeLog(changeLog);

                    return updateStatus ? AjaxResult.success(user) : AjaxResult.error("换绑错误，请联系管理员");
                } else {
                    PdChangeLog changeLog = new PdChangeLog();
                    changeLog.setChangeStatus("N");
                    changeLog.setType(type);
                    changeLog.setFromIp(oldIp);
                    changeLog.setToIp(newIp);
                    changeLog.setsCode(sCode);
                    changeLog.setStatusMsg("换绑失败，安全码类型与使用软件不匹配。");
                    pdChangeLogService.insertPdChangeLog(changeLog);
                    return AjaxResult.error("换绑失败，安全码类型与使用软件不匹配。");
                }
            } else {
                PdChangeLog changeLog = new PdChangeLog();
                changeLog.setChangeStatus("N");
                changeLog.setType(type);
                changeLog.setFromIp(oldIp);
                changeLog.setToIp(newIp);
                changeLog.setsCode(sCode);
                changeLog.setStatusMsg("安全码绑定的IP已到期，换绑失败");
                pdChangeLogService.insertPdChangeLog(changeLog);
                return AjaxResult.error("安全码绑定的IP已到期，换绑失败");
            }
        }

        String tip = "安全码不存在,请勿多次尝试，否则IP将被拉黑。";

        PdChangeLog changeLog = new PdChangeLog();
        changeLog.setChangeStatus("N");
        changeLog.setType(type);
        changeLog.setFromIp("");
        changeLog.setToIp(newIp);
        changeLog.setsCode(sCode);
        changeLog.setStatusMsg(tip);
        pdChangeLogService.insertPdChangeLog(changeLog);

        return AjaxResult.error(tip);
    }

    // 一键部署
    @Override
    public AjaxResult doInstall(
            String ip,//架设的服务器IP，可能为手动输入的虚拟机ip
            String reqIp,//请求的IP，用来检查授权信息
            String serverName,//区组名称
            String lineBaseName,//线路名称
            Integer portAaa,//验证端口
            Integer lineNumber,//线路数量
            String mysqlPsw//数据库密码
    ) {
        if (ip == null || ip.isEmpty()) {
            return AjaxResult.error("服务器IP获取失败！");
        }

        if (serverName == null || serverName.isEmpty()) {
            return AjaxResult.error("区组名称不能为空！");
        }

        if (mysqlPsw == null || mysqlPsw.isEmpty()) {
            return AjaxResult.error("数据库密码不能为空！");
        }

        if (lineNumber <= 0) {
            return AjaxResult.error("线路数量不能为0！");
        }

        if (portAaa < 1 || portAaa > 65535) {
            return AjaxResult.error("安全验证端口填写错误，正确范围：1-65535");
        }

        //鉴权 根据reqIp和类型0 工具箱 搜索用户，找到则表示鉴权通过
        PdUser user = pdUserMapper.getUserByIpAndUserType(reqIp, 0);
        if (user != null) {
            if ("Y".equals(user.getBlack())) {
                return AjaxResult.error(user.getBlackDec());
            }

            if (new Date().before(user.getAuthEndTime())) {
                //这两个变量是给脚本回传的
                StringBuilder lineNames = new StringBuilder();
                StringBuilder linePorts = new StringBuilder();
                List<HashMap<String, Object>> lineData = new ArrayList<>();

                //可以架设
                String baseTmpDir = "/tmp";

                File baseDir = new File(baseTmpDir);
                if (!baseDir.exists()) {
                    baseDir.mkdirs();
                }

                int randomInt = new Random().nextInt(999999999) + 1;

                //在临时目录下创建服务端配置文件存储的文件夹
                String serverConfigTmpDir = baseTmpDir + "/serverConf_" + randomInt;
                new File(serverConfigTmpDir).mkdirs();
                //在创建的文件夹下分别创建两个文件夹 存储线路配置和执行文件

                String serverConfigPath = serverConfigTmpDir + "/serverConf";
                String serverExecConfigPath = serverConfigTmpDir + "/serverExec";

                new File(serverConfigPath).mkdirs();
                new File(serverExecConfigPath).mkdirs();

                //创建成功
                logger.info("临时目录创建成功！");

                //写出aaa配置文件 没有中文 随便编码即可
                FileUtil.writeBytes(
                        String.format(CONFIG_AAA, ip, mysqlPsw, ip, mysqlPsw).getBytes(),
                        serverConfigTmpDir + "/aaa.ini"
                );

                //写出dba配置文件 没有中文 随便编码即可
                FileUtil.writeBytes(
                        String.format(CONFIG_DBA, ip, mysqlPsw, ip, mysqlPsw, ip, mysqlPsw, ip, portAaa).getBytes(),
                        serverConfigTmpDir + "/dba.ini"
                );

                //写出ccs配置文件 没有中文 随便编码即可
                FileUtil.writeBytes(
                        String.format(CONFIG_CCS, ip, mysqlPsw, ip, mysqlPsw, ip, mysqlPsw, ip, mysqlPsw, ip, portAaa).getBytes(),
                        serverConfigTmpDir + "/ccs.ini"
                );

                //写出csa配置文件 没有中文 随便编码即可
                FileUtil.writeBytes(
                        String.format(CONFIG_CSA, ip, mysqlPsw, ip, mysqlPsw, ip, mysqlPsw, ip, mysqlPsw, ip, portAaa).getBytes(),
                        serverConfigTmpDir + "/csa.ini"
                );

                //写出线路配置文件 和 执行文件
                StringBuilder tempSqlLine = new StringBuilder();
                //配置活动表用
                String line1Name = "";
                String line2Name = "";
                String line3Name = "";

                for (int i = 1; i <= lineNumber; i++) {
                    String lineName = lineBaseName + DigitalToChineseUtil.int2chineseNum(i);
                    if (i == 1) {
                        line1Name = lineName;
                    }

                    if (i == 2) {
                        line2Name = lineName;
                    }

                    if (i == 3) {
                        line3Name = lineName;
                    }

                    int linePort = 8159 + i;
                    HashMap<String, Object> lineInfo = new HashMap<>();
                    lineInfo.put("port", linePort);
                    lineInfo.put("name", lineName);
                    lineData.add(lineInfo);
                    linePorts.append(linePort).append(",");
                    lineNames.append(lineName).append(",");
                    
                    FileUtil.writeBytes(
                            String.format(CONFIG_LINE, lineName, ip, portAaa, linePort).getBytes(),
                            serverConfigPath + "/" + i + ".ini"
                    );

                    FileUtil.writeBytes(
                            String.format(EXEC_LINE, i).getBytes(),
                            serverExecConfigPath + "/" + i
                    );

                    tempSqlLine.append("('").append(lineName).append("','','").append(ip).append("',")
                            .append(linePort).append(",2000,'").append(serverName).append("',1,")
                            .append(i == 1 ? "'network'" : "''").append(",'','','',0,0,0,")
                            .append(i == 1 ? "600" : "0").append(",").append(i).append(",")
                            .append(i == 1 ? "'apex1'" : "''").append(",'','',0,NULL),");
                }

                if (lineNames.length() > 0) {
                    lineNames.setLength(lineNames.length() - 1);
                }
                
                if (linePorts.length() > 0) {
                    linePorts.setLength(linePorts.length() - 1);
                }

                if (tempSqlLine.length() > 0) {
                    tempSqlLine.setLength(tempSqlLine.length() - 1);
                }

                //读取内置的ETC模版内容列表
                List<EtcBean> etcList = new ArrayList<>();
                //invoke_etcs
                List<SysDictData> etcDictList = DictUtils.getDictCache("invoke_etcs");
                for (SysDictData dictData : etcDictList) {
                    String title = dictData.getDictLabel();
                    String downloadUrl = dictData.getDictValue();
                    String downloadAtt = dictData.getRemark();
                    etcList.add(new EtcBean(title, downloadUrl, downloadAtt));
                }

                if (etcList.isEmpty()) {
                    return AjaxResult.error("Etc模版目前为空。暂不支持架设。");
                }

                //写出sql文件
                try {
                    // 注意：这里需要从资源文件中读取asktao.sql
                    // this.javaClass.getResourceAsStream("/res/asktao.sql")!!.readAllBytes().toString(charset("GB2312")).let {
                    // 由于Java中读取资源文件的方式不同，此处需要调整
                    // 简化处理，实际应读取资源文件
                    String sql = "示例SQL内容"; // 这里应该是实际的SQL内容
                    byte[] sqlBytes = sql.replace("AAA端口", String.valueOf(portAaa))
                            .replace("DBA端口", "8120")
                            .replace("CCS端口", "8110")
                            .replace("CSA端口", "6101")
                            .replace("区组名称", serverName)
                            .replace("服务器IP", ip)
                            .replace("线路SQL", tempSqlLine.toString())
                            .replace("一线名称", line1Name)
                            .replace("二线名称", line2Name)
                            .replace("三线名称", line3Name)
                            .getBytes("GB2312");

                    FileUtil.writeBytes(sqlBytes, serverConfigTmpDir + "/asktao.sql");
                } catch (Exception e) {
                    logger.error("处理SQL文件时出错", e);
                    return AjaxResult.error("生成数据异常");
                }

                //压缩所有配置文件和sql 上传七牛云
                ZipUtil.zip(serverConfigPath, serverConfigTmpDir + "/serverConf.zip");
                FileUtil.del(serverConfigPath);

                ZipUtil.zip(serverExecConfigPath, serverConfigTmpDir + "/serverExec.zip");
                FileUtil.del(serverExecConfigPath);

                File asktaoZip = ZipUtil.zip(serverConfigTmpDir, baseTmpDir + "/asktao_" + randomInt + ".zip");
                FileUtil.del(serverConfigTmpDir);

                String keyBase = "yjjs/users_config_files/asktao_" + randomInt + ".zip";

                logger.info("开始上传阿里云...||路径为:{}", keyBase);
                //文件上传七牛云
                try {
                    // 注意：QnUploadUtil.uploadAliyun需要正确实现
                    String downloadUrl = "示例下载链接"; // 这应该是实际的上传结果
                    // downloadUrl = QnUploadUtil.uploadAliyun(keyBase, asktaoZip)!!;

                    HashMap<String, Object> result = new HashMap<>();
                    result.put("zipFileDownloadUrl", downloadUrl);
                    result.put("linePorts", linePorts.toString());
                    result.put("lineNames", lineNames.toString());
                    result.put("lineData", lineData);
                    result.put("etcList", etcList);

                    FileUtil.del(asktaoZip);
                    logger.info("删除压缩文件成功！");

                    return AjaxResult.success(result);
                } catch (Exception e) {
                    logger.error("配置文件上传到阿里云仓库失败！", e);
                    FileUtil.del(asktaoZip);
                    logger.info("删除压缩文件成功！");
                    return AjaxResult.error("生成数据异常");
                }

            } else {
                return AjaxResult.error("授权已到期，操作失败");
            }
        }
        return AjaxResult.error("未找到授权信息，请检查授权是否有效。");
    }

    // 生成快捷方式
    @SneakyThrows
    @Override
    public AjaxResult getBat(String reqIp, String serverName, Integer port) {
        PdUser user = pdUserMapper.getUserByIpAndUserType(reqIp, 0);
        if (user != null) {
            if (new Date().before(user.getAuthEndTime())) {
                String bat = "/tmp/" + reqIp + ".bat";

                String batContent = "Start asktao.mod " + serverName + ";" + reqIp + ";" + port + ";kbd:0;swictch:0;paroxy:0;flag:;uncheck";
                FileUtil.writeBytes(batContent.getBytes("GBK"), bat);
                
                // 注意：QnUploadUtil.uploadAliyun需要正确实现
                String downloadUrl = "示例下载链接"; // 这应该是实际的上传结果
                // downloadUrl = QnUploadUtil.uploadAliyun("yjjs/tmp/" + reqIp + ".bat", new File(bat))!!;

                FileUtil.del(bat);

                return AjaxResult.success("生成成功", downloadUrl);
            } else {
                return AjaxResult.error("授权已到期，操作失败");
            }
        }

        return AjaxResult.error("未找到授权信息，请检查授权是否有效。");
    }

    // 绑定QQ
    @Override
    public AjaxResult bindQQ(String qq, Integer type, String reqIp) {
        //获取用户
        PdUser user = pdUserMapper.getUserByIpAndUserType(reqIp, type);
        if (user != null) {
            if (new Date().before(user.getAuthEndTime())) {
                user.setBindQq(qq);
                user.setUpdateTime(DateUtils.getNowDate());
                pdUserMapper.updatePdUser(user);
                return AjaxResult.success(user);
            } else {
                return AjaxResult.error("授权已到期，操作失败");
            }
        }
        return AjaxResult.error("未找到授权信息，请检查授权是否有效。");
    }
}